﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using NDF.XnaLibrary.Models;
using Microsoft.Xna.Framework;

namespace NDF.ConnectionLibrary
{
	/// <summary>
	/// A helper class used to parse different strings to valuable information such as connection time, server adress, etc.
	/// </summary>
	public static class Parser
	{
		//A private stringbuilder used by the stringifying methods
		private static StringBuilder Builder = new StringBuilder(ServerNotificiationLength);

		/// <summary>
		/// Represents the length of a stringified ServerNotification.
		/// </summary>
		public static readonly int ServerNotificiationLength = 75;
		/// <summary>
		/// Represents the length of a single information received from a client.
		/// </summary>
		public static readonly int ClientInformationLength = 50;
		/// <summary>
		/// Represents the maximum length of a reason for disconnecting.
		/// </summary>
		public static readonly int MaxDisconnectedReasonLength = 35;
		/// <summary>
		/// Represents the encoding format to be used by both the server and the client.
		/// </summary>
		public static readonly Encoding EncodingFormat = Encoding.UTF8;
	
		/// <summary>
		/// Parses a server end point from the given Textreader.
		/// <para>The text must be in the following format:</para>
		///	<para>ServerIP: [IP]</para>
		///	<para>ServerPort: [Port]</para>
		///	</summary>
		/// <param name="text">The text to parse from.</param>
		/// <returns>The server IPEndPoint.</returns>
		private static IPAddress ParseServerIP(TextReader text)
		{
			string ipString = text.ReadLine().Replace("ServerIP:", "");
			string[] ipOctates = ipString.Split('.');
			byte[] ipAddress = new byte[ipOctates.Length];

			for (int i = 0; i < ipOctates.Length; i++)
			{
				ipAddress[i] = Byte.Parse(ipOctates[i]);
			}

			IPAddress ip = new IPAddress(ipAddress);
			return ip;			
		}

		private static int[] ParsePorts(TextReader text)
		{
			string clientToServer = text.ReadLine().Replace("ClientToServerPort:", "");
			int clientToServerPort = Int32.Parse(clientToServer);

			string serverToClient = text.ReadLine().Replace("ServerToClientPort:", "");
			int serverToClientPort = Int32.Parse(serverToClient);

			return new Int32[] { clientToServerPort, serverToClientPort } ;
		}

		private static Vector3 ParseVector3(string[] values)
		{
			Vector3 vector = new Vector3(); 
			vector.X = Single.Parse(values[0]);
			vector.Y = Single.Parse(values[1]);
			vector.Z = Single.Parse(values[2]);

			return vector;
		}

		private static Quaternion ParseQuaternion(string[] values)
		{
			Quaternion quaternion = new Quaternion();
			quaternion.X = Single.Parse(values[0]);
			quaternion.Y = Single.Parse(values[1]);
			quaternion.Z = Single.Parse(values[2]);
			quaternion.W = Single.Parse(values[3]);
			
			return quaternion;
		}

		/// <summary>
		/// Gets a string representing the given quaternion and message.
		/// </summary>
		/// <param name="quaternion">The quaternion to be stringified.</param>
		/// <param name="message">The message to accompany the quaternion.</param>
		public static string StringifyQuaternion(Quaternion quaternion, string message)
		{
			return string.Format("{0}: {1} {2} {3} {4}", message,
						quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
		}

		/// <summary>
		/// Gets a string representing the given quaternion and message.
		/// </summary>
		/// <param name="vector">The vector3 to be stringified.</param>
		/// <param name="message">The message to accompany the vector3.</param>
		public static string StringifyVector3(Vector3 vector, string message)
		{
			return string.Format("{0}: {1} {2} {3}", message, vector.X, vector.Y, vector.Z);
		}

		/// <summary>
		/// Parses the given text and returns a ServerSettings struct holding all the read information.
		/// </summary>
		/// <param name="text">The text to be read (string or file).</param>
		/// <returns>The ServerSettings struct.</returns>
		public static ServerSettings ParseServerSettings(TextReader text)
		{
			ServerSettings settings = new ServerSettings();
			settings.IP = ParseServerIP(text);
			int[] ports = ParsePorts(text);
			settings.ClientToServerPort = ports[0];
			settings.ServerToClientPort = ports[1];

			return settings;
		}

		/// <summary>
		/// Stringifies a Dictionary(int, CharacterInfo) containing multiple character information.
		/// </summary>
		/// <param name="characters">The dictionary to be stringified</param>
		/// <returns>String representing the dictionary.</returns>
		public static string StringifyCharacterInfoDictionary(Dictionary<int, CharacterInfo> characters)
		{
			if (characters.Count == 0)
				return "-";

			CharacterInfo info;
			foreach (var character in characters)
			{
				info = character.Value;
				Builder.AppendLine("CharacterType: " + info.Type);

				Builder.AppendLine("ID: " + character.Key);
				Builder.AppendLine("State: " + info.State.ToString());
				Builder.AppendLine(StringifyVector3(info.Position, "Position"));
				Builder.AppendLine(StringifyQuaternion(info.Rotation, "Rotating"));
				if (info.State == CharacterState.Moving || info.State == CharacterState.MovingAndPostCasting)
				{
					Builder.AppendLine(StringifyVector3(info.WalkingDirection, "Walking"));
				}
				if (info.State == CharacterState.Casting || info.State == CharacterState.PostCasting || 
					info.State == CharacterState.MovingAndPostCasting)
				{
					Builder.AppendLine("Casting: " + info.CastingActionInfo.SkillId);
				}					
				Builder.AppendLine("-");
			}

			string text = Builder.ToString();
			Builder.Clear();
			return text;
		}

		/// <summary>
		/// Parses a given text and returns a Dictionary(int, CharacterInfo) containing multiple character information.
		/// </summary>
		/// <param name="text">The text to read from.</param>
		public static Dictionary<int, CharacterInfo> ParseMultipleCharacterInformation(TextReader text)
		{
			Dictionary<int, CharacterInfo> playerCharacters = new Dictionary<int, CharacterInfo>();
			string line = text.ReadLine();
			if (string.IsNullOrEmpty(line) || line == "-")
				return playerCharacters;

			while (!string.IsNullOrEmpty(line))
			{
				CharacterInfo info = new CharacterInfo();
				if (line.Contains("NPC"))
					info.Type = CharacterType.NPC;
				else
					info.Type = CharacterType.Player;

				line = text.ReadLine();

				int id = Int32.Parse(line.Replace("ID:", ""));

				line = text.ReadLine();
				info.State = (CharacterState) Enum.Parse(typeof(CharacterState), line.Replace("State: ", ""));

				line = text.ReadLine();
				string[] position = line.Replace("Position: ", "").Split(' ');
				info.Position = ParseVector3(position);

				line = text.ReadLine();
				string[] rotation = line.Replace("Rotating: ", "").Split(' ');
				info.Rotation = ParseQuaternion(rotation);

				line = text.ReadLine();
				if (line == "-")
				{
					line = text.ReadLine();
					playerCharacters.Add(id, info);
					continue;
				}
				if (line.Contains("Walking"))
				{
					position = line.Replace("Walking: ", "").Split(' ');
					info.WalkingDirection = ParseVector3(position);
					line = text.ReadLine();
				}
				if (line == "-")
				{
					line = text.ReadLine();
					playerCharacters.Add(id, info);
					continue;
				}
				if (line.Contains("Casting"))
				{
					info.CastingActionInfo.SkillId = Int32.Parse(line.Replace("Casting: ", ""));
				}
				playerCharacters.Add(id, info);
			}
			return playerCharacters;
		}

		/// <summary>
		/// Parses the specified text and returns a ServerNotification object holding the text`s information.
		/// </summary>
		/// <param name="text">The text to be parsed into a ServerNotification.</param>
		public static ServerNotification ParseSingleCharacterInformation(TextReader text)
		{
			string line;
			line = text.ReadLine();
			ServerNotification notification = new ServerNotification();
			if (line.Contains("NPC"))
				notification.CharacterType = CharacterType.NPC;
			else
				notification.CharacterType = CharacterType.Player;
				
			line = text.ReadLine();
			notification.ID = Int32.Parse(line.Replace("ID:", ""));
			line = text.ReadLine();
			if (line.Contains("Rotating"))
			{
				notification.NotificationType = NotificationType.Rotating;
				notification.Data = ParseQuaternion(line.Replace("Rotating: ", "").Split(' '));
			}
			else if (line.Contains("Walking"))
			{	
				notification.NotificationType = NotificationType.Walking;
				notification.Data = ParseVector3(line.Replace("Walking: ", "").Split(' '));
			}
			else if (line.Contains("Casting"))
			{
				notification.NotificationType = NotificationType.Casting;
				notification.Data = Int32.Parse(line.Replace("Casting: ", ""));
			}
			else if (line.Contains("Connected"))
			{
				notification.NotificationType = NotificationType.Connected;
			}
			else
			{
				notification.NotificationType = NotificationType.Disconnected;
				notification.Data = line.Replace("Disconnected: ", "").Replace("\0", "");
			}

			return notification;
		}

		/// <summary>
		/// Stringifies the specified ServerNotification object.
		/// </summary>
		/// <param name="notification">The notification to be stringified.</param>
		public static string StringifyServerNotification(ServerNotification notification)
		{
			Builder.AppendLine("Type: " + (notification.CharacterType == CharacterType.NPC ? "NPC" : "Plr"));
			Builder.AppendLine("ID: " + notification.ID.ToString());
			switch(notification.NotificationType)
			{
				case NotificationType.Casting :
					Builder.AppendLine("Casting: " + notification.Data.ToString());
					break;
				case NotificationType.Walking :
					Builder.AppendLine(StringifyVector3((Vector3) notification.Data, "Walking"));
					break;
				case NotificationType.Rotating :
					Builder.AppendLine(StringifyQuaternion((Quaternion) notification.Data, "Rotating"));
					break;
				case NotificationType.Connected :
					Builder.AppendLine("Connected");
					break;
				case NotificationType.Disconnected :
					string reason = (string) notification.Data;
					if (reason.Length > 30)
						throw new ArgumentException("The data attribute cannot be longer than 30 characters");

					Builder.AppendLine("Disconnected: " + reason);
					break;
			}
			while (Builder.Length < ServerNotificiationLength)
			{
				Builder.Append("-");
			}
			string text = Builder.ToString();
			Builder.Clear();
			return text;
		}

		/// <summary>
		/// Creates and returns a new ServerNotification object with the specified information.
		/// </summary>
		/// <param name="id">The character`s id.</param>
		/// <param name="characterType">The type of the character.</param>
		/// <param name="information">String holding information to be parsed.</param>
		public static ServerNotification CreateServerNotification(int id, CharacterType characterType, string information)
		{
			ServerNotification notification = new ServerNotification();
			notification.CharacterType = characterType;
			notification.ID = id;
			if (information.Contains("Rotating"))
			{
				notification.NotificationType = NotificationType.Rotating;
				notification.Data = ParseQuaternion(information.Replace("Rotating: ", "").Split(' '));
			}
			else if (information.Contains("Walking"))
			{
				notification.NotificationType = NotificationType.Walking;
				notification.Data = ParseVector3(information.Replace("Walking: ", "").Split(' '));
			}
			else if (information.Contains("Casting"))
			{
				notification.NotificationType = NotificationType.Casting;
				notification.Data = Int32.Parse(information.Replace("Casting: ", ""));
			}
			else if (information.Contains("Connected"))
			{
				notification.NotificationType = NotificationType.Connected;
			}
			else
			{
				notification.NotificationType = NotificationType.Disconnected;
				notification.Data = information.Replace("Disconnected: ", "").Replace("\0", "");
			}

			return notification;
		}
	}
}
